#include "lgfx.h"
#include <Adafruit_AHTX0.h>
#include <Adafruit_NeoPixel.h>
#include <ArduinoOTA.h>
#include <Audio.h>
#include <HTTPClient.h>
#include <OneButton.h>
#include <map>
#include <Preferences.h>
#include <esp_ota_ops.h> 

// ================ 硬件引脚定义 ================ //
// LED相关引脚
#define PIN_LED         48        // RGB LED灯带数据引脚
#define PIN_RGB_LED     47        // 单个RGB LED引脚
#define PIN_LCD_BL      15        // LCD背光控制引脚

// I2S音频接口引脚
#define PIN_I2S_DOUT    5         // I2S数据输出
#define PIN_I2S_BCLK    6         // I2S时钟信号
#define PIN_I2S_LRC     7         // I2S左右声道选择

// 按键和电源管理引脚
#define PIN_KEY_ADD     21        // 音量加/下一首
#define PIN_KEY_MINUS   9         // 音量减/上一首
#define PIN_KEY_MODE    0         // 模式切换键
#define PIN_KEY_POWER   42        // 电源键
#define PIN_POWER_SWITCH 14       // 电源控制引脚
#define PIN_BAT_ADC      1        // 电池电压检测引脚
#define PIN_BAT_CHARGING   2      // 充电状态检测引脚

// SD卡引脚
#define PIN_SD_CS 10
#define PIN_SD_MOSI 11
#define PIN_SD_MISO 13
#define PIN_SD_SCK 12

// ================ 显示相关常量 ================ //
// 屏幕布局
#define SCREEN_WIDTH    240       // 屏幕宽度
#define SCREEN_HEIGHT   240       // 屏幕高度
#define IP_Y_POS       220       // IP地址显示Y坐标
#define RADIO_INFO_Y_POS 35      // 电台信息Y坐标
#define TIME_Y_POS     120       // 时间显示Y坐标
#define DATE_Y_POS     105       // 日期显示Y坐标

// 音量控制参数
#define VOLUME_BAR_WIDTH  140     // 音量条宽度
#define VOLUME_BAR_HEIGHT 10      // 音量条高度
#define VOLUME_MAX       21       // 最大音量值

// 时间显示常量
#define TIME_Y_POS 120         // 时间显示Y坐标
#define DATE_Y_POS 105         // 日期显示Y坐标

// 颜色定义
#define TFT_BLACK     0x0000
#define TFT_WHITE     0xFFFF
#define TFT_CYAN      0x07FF
#define TFT_ORANGE    0xFD20
#define TFT_BG_COLOR  0x18E0  // 深蓝色背景
#define TFT_PRIMARY_COLOR TFT_CYAN
#define TFT_SECONDARY_COLOR TFT_WHITE
#define TFT_HIGHLIGHT_COLOR 0xFD20  // 橙色
#define TFT_WARNING_COLOR TFT_YELLOW
#define TFT_ERROR_COLOR TFT_RED
#define TFT_TECH_BORDER   0x4D3C  // 科技蓝边框色
#define TFT_TECH_LINE     0x2B5D  // 深蓝装饰线

// 字体定义
#define FONT_SMALL &fonts::efontCN_16  // 小字体
#define FONT_MEDIUM &fonts::FreeSans18pt7b  // 中等字体
#define FONT_LARGE &fonts::FreeSans24pt7b  // 改为粗体大字体

#define BAT_MAX_VOL     4.2       // 满电电压
#define BAT_MIN_VOL     3.3       // 最低电压
#define ADC_RESOLUTION  4095.0    // ADC分辨率
#define ADC_REF_VOL     3.3       // ADC参考电压
#define BAT_VOLTAGE_CALIBRATION 1.046  // 电压校准系数

#define BAT_SHUTDOWN_VOL  3.3       // 自动关机电压阈值
#define BAT_WARNING_VOL   3.5      // 低电量警告电压阈值
#define LOW_BAT_CHECK_INTERVAL 60000 // 低电量检查间隔(ms)
////

using namespace std;

#define FONT16 &fonts::efontCN_16

typedef struct {
  String name;
  String path;
} SongItem;

static const char *WEEK_DAYS[] = {"日", "一", "二", "三", "四", "五", "六"};
Adafruit_NeoPixel pixels(4, PIN_LED, NEO_GRB + NEO_KHZ800);
Adafruit_AHTX0 aht;
LGFX tft;
LGFX_Sprite sp(&tft);
long check1s = 0, check10ms = 0, check300ms = 0, check5s = 0;
char buf[128] = {0};
Audio audio;
int curIndex = 0;
int curVolume = 8;
vector<SongItem *> songs;
std::map<u32_t, OneButton *> buttons;
String sliders[3];
uint8_t sildeIdx = 0;

void inline initTFTDevice() {
  tft.init();
  tft.setBrightness(60);
  tft.setFont(FONT16);
  tft.setColorDepth(8);
  tft.fillScreen(TFT_BLACK);
  sp.setFont(FONT16);
  sp.setColorDepth(8);
}

void inline showPlayProgress() {
  uint32_t act = audio.getAudioCurrentTime();
  uint32_t afd = audio.getAudioFileDuration();
  sp.createSprite(240, 32);
  sp.drawRoundRect(18, 0, 204, 10, 3, TFT_ORANGE);
  if (act > 0 && afd > 0) {
    int prog = act * 200 / afd;
    sp.fillRoundRect(20, 2, prog, 6, 2, TFT_GREEN);
    sprintf(buf, "%02i:%02d", (act / 60), (act % 60));
    sp.drawString(buf, 20, 16);
    sprintf(buf, "%02i:%02d", (afd / 60), (afd % 60));
    sp.drawRightString(buf, 220, 16);
  }
  sp.pushSprite(0, 50);
  sp.deleteSprite();
}

void playNext(int offset) {
  audio.stopSong();
  int total = songs.size();
  if (total > 0) {
    curIndex += offset;
    if (curIndex >= total) {
      curIndex %= total;
    } else if (curIndex < 0) {
      curIndex += total;
    }
    sprintf(buf, "正在播放: %d/%d", curIndex + 1, total);
    sliders[2] = buf;
    auto *song = songs[curIndex];
    auto *name = song->name.c_str();
    sprintf(buf, "%d.%s", curIndex + 1, name);
    sp.createSprite(240, 16);
    sp.drawCentreString(buf, 120, 0);
    sp.pushSprite(0, 20);
    sp.deleteSprite();
    audio.connecttoFS(SD, song->path.c_str());
  }
}

void nextVolume(int offset) {
  int vol = curVolume + offset;
  if (vol >= 0 && vol <= 21) {
    curVolume = vol;
    audio.setVolume(curVolume);
    sprintf(buf, "音量: %d", curVolume);
    sp.createSprite(120, 16);
    sp.drawString(buf, 8, 0);
    sp.pushSprite(0, 220);
    sp.deleteSprite();
  }
}

void inline initAudioDevice() {
  audio.setPinout(PIN_I2S_BCLK, PIN_I2S_LRC, PIN_I2S_DOUT);
  audio.setVolume(curVolume);
}

void inline initPixels() {
  pinMode(PIN_RGB_LED, OUTPUT);
  digitalWrite(PIN_RGB_LED, LOW);
  pixels.begin();
  pixels.setBrightness(40);
  pixels.clear();
  pixels.show();
}

void inline autoConfigWifi() {
  tft.println("Start WiFi Connect!");
  WiFi.mode(WIFI_MODE_STA);
  WiFi.begin();
  for (int i = 0; WiFi.status() != WL_CONNECTED && i < 100; i++) {
    delay(100);
  }
  if (WiFi.status() != WL_CONNECTED) {
    WiFi.mode(WIFI_MODE_APSTA);
    WiFi.beginSmartConfig();
    tft.println("Use ESPTouch App!");
    while (WiFi.status() != WL_CONNECTED) {
      delay(100);
    }
    WiFi.stopSmartConfig();
    WiFi.mode(WIFI_MODE_STA);
  }
  tft.println("WiFi Connected, Please Wait...");
}

inline void showCurrentTime() {
  struct tm info;
  getLocalTime(&info);
  sprintf(buf, "%d年%d月%d日 星期%s", 1900 + info.tm_year, info.tm_mon + 1,
          info.tm_mday, WEEK_DAYS[info.tm_wday]);
  sp.createSprite(240, 16);
  sp.drawCentreString(buf, 116, 0);
  sp.pushSprite(0, 140);
  sp.deleteSprite();
  strftime(buf, 36, "%T", &info);
  sp.createSprite(240, 36);
  sp.drawCentreString(buf, 120, 0, &fonts::FreeSans24pt7b);
  sp.pushSprite(0, 160);
  sp.deleteSprite();
}

void inline startConfigTime() {
  const int timeZone = 8 * 3600;
  configTime(timeZone, 0, "ntp6.aliyun.com", "cn.ntp.org.cn", "ntp.ntsc.ac.cn");
  while (time(nullptr) < 8 * 3600 * 2) {
    delay(300);
  }
}

void inline setupOTAConfig() {
  ArduinoOTA.onStart([] {
    audio.stopSong();
    tft.setBrightness(200);
    tft.clear();
    tft.setTextColor(TFT_WHITE, TFT_BLACK);
    tft.drawCentreString("软件升级", 120, 8, FONT16);
    tft.drawRoundRect(18, 158, 204, 10, 3, TFT_ORANGE);
    tft.drawCentreString("正在升级中，请勿断电...", 120, 190, FONT16);
  });
  ArduinoOTA.onProgress([](u32_t pro, u32_t total) {
    sprintf(buf, "升级进度: %d / %d", pro, total);
    tft.setTextColor(TFT_YELLOW, TFT_BLACK);
    tft.drawCentreString(buf, 120, 120, FONT16);
    if (pro > 0 && total > 0) {
      int pros = pro * 200 / total;
      tft.fillRoundRect(20, 160, pros, 6, 2, TFT_WHITE);
    }
  });
  ArduinoOTA.onEnd([] {
    tft.clear();
    tft.setTextColor(TFT_WHITE, TFT_BLACK);
    tft.drawCentreString("升级成功", 120, 60, FONT16);
    tft.drawCentreString("升级已完成，正在重启...", 120, 140, FONT16);
  });
  ArduinoOTA.onError([](ota_error_t e) {
    tft.clear();
    ESP.restart();
  });
  ArduinoOTA.begin();
  sprintf(buf, "%s", WiFi.localIP().toString().c_str());
  tft.println(buf);
  struct tm info;
  getLocalTime(&info);
  strftime(buf, 64, "%c", &info);
  tft.println(buf);
}

inline void showClientIP() {
  tft.clear();
  sprintf(buf, "%s", WiFi.localIP().toString().c_str());
  sp.createSprite(120, 16);
  sp.drawRightString(buf, 112, 0);
  sp.pushSprite(120, 220);
  sp.deleteSprite();
}

void onButtonClick(void *p) {
  u32_t pin = (u32_t)p;
  switch (pin) {
  case PIN_KEY_MODE:
    audio.pauseResume();
    break;
  case PIN_KEY_ADD:
    playNext(1);
    break;
  case PIN_KEY_MINUS:
    playNext(-1);
    break;
  default:
    break;
  }
}

void onButtonDoubleClick(void *p) {
  u32_t pin = (u32_t)p;
  switch (pin) {
  case PIN_KEY_ADD:
    nextVolume(1);
    break;
  case PIN_KEY_MINUS:
    nextVolume(-1);
    break;
  default:
    break;
  }
}

// 长按处理函数
void onButtonLongPressStart(void *p) {
  u32_t pin = (u32_t)p;
  LGFX_Sprite shutdownSprite(&tft);
  const esp_partition_t* next_partition;
  esp_err_t err;
  
  switch (pin) {
    case PIN_KEY_ADD:
    shutdownSprite.createSprite(240, 60);
    shutdownSprite.fillSprite(TFT_BLACK);
    shutdownSprite.setTextColor(TFT_ERROR_COLOR, TFT_BLACK);
    shutdownSprite.setFont(FONT_SMALL);
    next_partition = esp_ota_get_next_update_partition(NULL);
    if (next_partition == NULL)
    {
      shutdownSprite.drawCentreString("未找到其他分区", 120, 10);
      shutdownSprite.pushSprite(0, 90);
      delay(1000);
      tft.clear();  
      shutdownSprite.deleteSprite();
      return;
    }
    err = esp_ota_set_boot_partition(next_partition);
    if (err != ESP_OK)
    {
      shutdownSprite.drawCentreString("切换应用失败", 120, 10);
      shutdownSprite.pushSprite(0, 90);
      delay(1000);
      tft.clear();  
      shutdownSprite.deleteSprite();
      return;
    }
    shutdownSprite.drawCentreString("应用切换成功，正在重启...", 120, 10);
    shutdownSprite.pushSprite(0, 90);
    delay(1500);
    shutdownSprite.deleteSprite();
    esp_restart();
    break;
  case PIN_KEY_POWER:
      tft.clear();
      shutdownSprite.createSprite(240, 60);
      shutdownSprite.fillSprite(TFT_BLACK);
      shutdownSprite.setTextColor(TFT_ERROR_COLOR, TFT_BLACK);
      shutdownSprite.setFont(FONT_SMALL);
      shutdownSprite.drawCentreString("正在关机...", 120, 10);
      shutdownSprite.pushSprite(0, 90);
      shutdownSprite.deleteSprite();
      
      delay(1000);  // 显示关机信息
      //preferences.end();  // 保存设置
      tft.clear();       // 清屏
      digitalWrite(PIN_POWER_SWITCH, LOW);
      while(1); // 一直等到断电
      break;
  default:
      break;
  }
}

void inline setupButtons() {
  u32_t btnPins[] = {PIN_KEY_ADD, PIN_KEY_MINUS, PIN_KEY_MODE,PIN_KEY_POWER};
  for (auto pin : btnPins) {
    auto *btn = new OneButton(pin);
    btn->attachClick(onButtonClick, (void *)pin); //短按弹起事件注册
    btn->attachDoubleClick(onButtonDoubleClick, (void *)pin); //双击事件
    btn->attachLongPressStart(onButtonLongPressStart, (void *)pin); //长按开始事件
    buttons.insert({pin, btn});
  }
}

void inline showSlider() {
  sp.createSprite(240, 16);
  String txt = sliders[sildeIdx];
  sp.drawCentreString(txt, 110, 0);
  sp.pushSprite(0, 100);
  sp.deleteSprite();
}

void inline initSDCard() {
  pinMode(PIN_SD_CS, OUTPUT);
  digitalWrite(PIN_SD_CS, HIGH);
  SPI.begin(PIN_SD_SCK, PIN_SD_MISO, PIN_SD_MOSI);
  SD.begin(PIN_SD_CS);
}

void scanMp3InDir(fs::FS &fs, const char *dirname, uint8_t levels) {
  File root = fs.open(dirname);
  if (!root) {
    return;
  }
  if (!root.isDirectory()) {
    return;
  }
  File mp3 = root.openNextFile();
  while (mp3) {
    String path = mp3.path();
    if (mp3.isDirectory()) {
      if (levels) {
        scanMp3InDir(fs, mp3.path(), levels - 1);
      }
    } else if (path.endsWith(".mp3")) {
      SongItem *song = new SongItem();
      song->name = mp3.name();
      song->path = path;
      songs.push_back(song);
    }
    mp3 = root.openNextFile();
  }
}

void inline scanAllMusic() {
  scanMp3InDir(SD, "/", 3);
  auto allMb = SD.totalBytes() / (1024 * 1024);
  auto usedMb = SD.usedBytes() / (1024 * 1024);
  sprintf(buf, "TF卡已使用: %lluMB/%lluMB", usedMb, allMb);
  sliders[0] = buf;
}

void initPowerManager(){
  pinMode(PIN_BAT_CHARGING, INPUT_PULLUP);
  pinMode(PIN_KEY_POWER, INPUT);
  pinMode(PIN_POWER_SWITCH, OUTPUT);
  digitalWrite(PIN_POWER_SWITCH, HIGH);
}

void setup() {
  Serial.begin(115200);
  Serial.println("Hello ESP-S3!!");
  initPowerManager();
  initTFTDevice();
  setupButtons();
  initPixels();
  initSDCard();
  initAudioDevice();
  autoConfigWifi();
  startConfigTime();
  setupOTAConfig();
  showClientIP();
  scanAllMusic();
  nextVolume(0);
  playNext(0);
}

void loop() {
  audio.loop();
  auto ms = millis();
  if (ms - check5s > 5000) {
    check5s = ms;
    showSlider();
    sildeIdx = (sildeIdx + 1) % (sizeof(sliders) / sizeof(sliders[0]));
  }
  if (ms - check1s > 1000) {
    check1s = ms;
    ArduinoOTA.handle();
    showPlayProgress();
    digitalWrite(PIN_RGB_LED, check1s % 2 ? LOW : HIGH);
  }
  if (ms - check300ms > 300) {
    check300ms = ms;
    showCurrentTime();
    uint16_t rc = rand() % 65536;
    pixels.fill(rc);
    pixels.show();
  }
  if (ms - check10ms >= 10) {
    check10ms = ms;
    for (auto it : buttons) {
      it.second->tick();
    }
  }
}

void audio_eof_mp3(const char *info) { playNext(1); }